// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Util from "./Util";
import work from 'webworkify-webpack';
import PROTOCOL_CONFIG from  './config/protocolConfig';

const OPT_MIC_AUDIO_FRAME = PROTOCOL_CONFIG.MICROPHONE_MESSAGE_TYPE.OPT_MIC_AUDIO_FRAME;

export default class MicrophonePlayer {
    constructor(options) {
        this.options = {...options};
        this.sendOutBufHandler = options.sendOutBufHandler;
        this.util = new Util();
        if (this.options.audioOptions) {
            this.audioStream = null;
            this.audioSource = null;
            this.audioScriptNode = null;
            this.audioEncoderWorker = work(require.resolve('./worker/AudioEncoder.js'));
            this.audioEncoderWorker.addEventListener('message', this.audioMessageHandler.bind(this));
            this.initMediaDevices(this.options.audioOptions);
        }
    }

    initMediaDevices(audioOptions) {
        if (navigator.mediaDevices === undefined) {
            navigator.mediaDevices = {};
        }

        if (navigator.mediaDevices.getUserMedia === undefined) {
            navigator.mediaDevices.getUserMedia = (constraints) => {
        
            let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia || navigator.msGetUserMedia;

            if (!getUserMedia) {
                return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
            }

            return new Promise((resolve, reject) => {
                getUserMedia.call(navigator, constraints, resolve, reject);
            });
            }
        }
        
        navigator.mediaDevices.getUserMedia({ video: false, audio: audioOptions })
        .then((stream) => {
            // 音频轨道
            this.audioStream = stream.getAudioTracks()[0];
            // 打开麦克风
            if (this.audioStream) {
                this.audioStreamHandle();
            }
        })
        .catch((err) => {
            console.log(err.name + ": " + err.message);
        });
    }

    audioStreamHandle() {
        const newAudioStream = new MediaStream();
        newAudioStream.addTrack(this.audioStream);
        const audioContext = new AudioContext();
        this.audioSource = audioContext.createMediaStreamSource(newAudioStream);
        this.audioScriptNode = audioContext.createScriptProcessor(4096, 1, 1);
        this.audioScriptNode.onaudioprocess = (audioProcessEvent) => {
            // 获取输入缓冲区
            const inputBuffer = audioProcessEvent.inputBuffer;
            const pcmData = inputBuffer.getChannelData(0);

            if (this.options.microPhoneOutputType === 'PCM') {
                // 转换为16位整数
                let initData = new Int16Array(pcmData.length);
                for(let i=0; i<pcmData.length; i++) {
                    initData[i] = pcmData[i] * 32767;
                }
                // 转换为little endian格式
                let littleEndianData = new Uint8Array(initData.length * 2);
                for(let i=0; i<initData.length; i++) {
                    littleEndianData[i * 2] = initData[i] & 0xFF;
                    littleEndianData[i * 2 + 1] = (initData[i] >> 8) & 0xFF;
                }

                this.sendOutBufHandler('MICROPHONE', OPT_MIC_AUDIO_FRAME, littleEndianData);
            } else {
                // 处理PCM音频数据
                this.audioEncoderWorker.postMessage({
                    type: 'encode',
                    data: pcmData
                }, [pcmData.buffer]);
            }
        }

        this.audioSource.connect(this.audioScriptNode);
        this.audioScriptNode.connect(audioContext.destination);
    }

    audioMessageHandler(evt) {
        const message = evt.data;
        if (!Object.prototype.hasOwnProperty.call(message, 'type')) {
            return;
        }

        switch (message.type) {
            case 'encodeResult': {
                const opusData = message.data;
                opusData.forEach(itemData => {
                    const itemBuffer = new Uint8Array(itemData);
                    this.sendOutBufHandler('MICROPHONE', OPT_MIC_AUDIO_FRAME, itemBuffer);
                });
                break;
            }
            default:
                break;
        }
    }

    stopAudio() {
        if(this.audioStream) {
            this.audioStream.stop();
            this.audioSource.disconnect();
            this.audioScriptNode.disconnect();
            this.audioScriptNode.onaudioprocess = null;
        }
    }
}